package com.ruoyi.generator.service

import com.alibaba.fastjson2.JSON
import com.ruoyi.common.constant.Constants
import com.ruoyi.common.constant.GenConstants
import com.ruoyi.common.core.text.CharsetKit
import com.ruoyi.common.exception.ServiceException
import com.ruoyi.common.utils.MyStringUtils
import com.ruoyi.generator.domain.GenTable
import com.ruoyi.generator.domain.GenTableColumn
import com.ruoyi.generator.mapper.GenTableColumnMapper
import com.ruoyi.generator.mapper.GenTableMapper
import com.ruoyi.generator.util.GenUtils
import com.ruoyi.generator.util.VelocityInitializer
import com.ruoyi.generator.util.VelocityUtils
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.apache.velocity.app.Velocity
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.io.StringWriter
import java.util.function.Consumer
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

/**
 * 业务 服务层实现
 *
 * @author ruoyi
 */
@Service
open class GenTableServiceImpl : IGenTableService {
    @Autowired
    private lateinit var genTableMapper: GenTableMapper

    @Autowired
    private lateinit var genTableColumnMapper: GenTableColumnMapper

    /**
     * 查询业务信息
     *
     * @param id 业务ID
     * @return 业务信息
     */
    override fun selectGenTableById(id: Long?): GenTable? {
        val genTable = genTableMapper.selectGenTableById(id)
        setTableFromOptions(genTable)
        return genTable
    }

    /**
     * 查询业务列表
     *
     * @param genTable 业务信息
     * @return 业务集合
     */
    override fun selectGenTableList(genTable: GenTable?): List<GenTable> {
        return genTableMapper.selectGenTableList(genTable)
    }

    /**
     * 查询据库列表
     *
     * @param genTable 业务信息
     * @return 数据库表集合
     */
    override fun selectDbTableList(genTable: GenTable?): List<GenTable> {
        return genTableMapper.selectDbTableList(genTable)
    }

    /**
     * 查询据库列表
     *
     * @param tableNames 表名称组
     * @return 数据库表集合
     */
    override fun selectDbTableListByNames(tableNames: Array<String>?): List<GenTable> {
        return genTableMapper.selectDbTableListByNames(tableNames)
    }

    /**
     * 查询所有表信息
     *
     * @return 表信息集合
     */
    override fun selectGenTableAll(): List<GenTable> {
        return genTableMapper.selectGenTableAll()
    }

    /**
     * 修改业务
     *
     * @param genTable 业务信息
     * @return 结果
     */
    @Transactional
    override fun updateGenTable(genTable: GenTable) {
        val options = JSON.toJSONString(genTable.params)
        genTable.options = options
        val row = genTableMapper.updateGenTable(genTable)
        if (row > 0) {
            for (cenTableColumn in genTable.columns!!) {
                genTableColumnMapper.updateGenTableColumn(cenTableColumn)
            }
        }
    }

    /**
     * 删除业务对象
     *
     * @param tableIds 需要删除的数据ID
     * @return 结果
     */
    @Transactional
    override fun deleteGenTableByIds(tableIds: Array<Long?>?) {
        genTableMapper.deleteGenTableByIds(tableIds)
        genTableColumnMapper.deleteGenTableColumnByIds(tableIds)
    }

    /**
     * 创建表
     *
     * @param sql 创建表语句
     * @return 结果
     */
    override fun createTable(sql: String?): Boolean {
        return genTableMapper.createTable(sql) == 0
    }

    /**
     * 导入表结构
     *
     * @param tableList 导入表列表
     */
    @Transactional
    override fun importGenTable(tableList: List<GenTable>?, operName: String?) {
        try {
            if (tableList != null) {
                for (table in tableList) {
                    val tableName = table.tableName
                    GenUtils.initTable(table, operName)
                    val row = genTableMapper.insertGenTable(table)
                    if (row > 0) {
                        // 保存列信息
                        val genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName)
                        for (column in genTableColumns) {
                            GenUtils.initColumnField(column, table)
                            genTableColumnMapper.insertGenTableColumn(column)
                        }
                    }
                }
            }
        } catch (e: Exception) {
            throw ServiceException("导入失败：" + e.message)
        }
    }

    /**
     * 预览代码
     *
     * @param tableId 表编号
     * @return 预览数据列表
     */
    override fun previewCode(tableId: Long?): Map<String, String> {
        val dataMap: MutableMap<String, String> = LinkedHashMap()
        // 查询表信息
        val table = genTableMapper.selectGenTableById(tableId)
        // 设置主子表信息
        setSubTable(table)
        // 设置主键列信息
        setPkColumn(table)
        VelocityInitializer.initVelocity()
        val context = table?.let { VelocityUtils.prepareContext(it) }

        // 获取模板列表
        val templates = table?.tplCategory?.let { table.tplWebType?.let { it1 -> VelocityUtils.getTemplateList(it, it1) } }
        if (templates != null) {
            for (template in templates) {
                // 渲染模板
                val sw = StringWriter()
                val tpl = Velocity.getTemplate(template, Constants.UTF8)
                tpl.merge(context, sw)
                dataMap[template] = sw.toString()
            }
        }
        return dataMap
    }

    /**
     * 生成代码（下载方式）
     *
     * @param tableName 表名称
     * @return 数据
     */
    override fun downloadCode(tableName: String): ByteArray {
        val outputStream = ByteArrayOutputStream()
        val zip = ZipOutputStream(outputStream)
        generatorCode(tableName, zip)
        IOUtils.closeQuietly(zip)
        return outputStream.toByteArray()
    }

    /**
     * 生成代码（自定义路径）
     *
     * @param tableName 表名称
     */
    override fun generatorCode(tableName: String?) {
        // 查询表信息
        val table = genTableMapper.selectGenTableByName(tableName)
        // 设置主子表信息
        setSubTable(table)
        // 设置主键列信息
        setPkColumn(table)
        VelocityInitializer.initVelocity()
        val context = table?.let { VelocityUtils.prepareContext(it) }

        // 获取模板列表
        val templates = table?.tplCategory?.let { table.tplWebType?.let { it1 -> VelocityUtils.getTemplateList(it, it1) } }
        if (templates != null) {
            for (template in templates) {
                if (!org.apache.commons.lang3.StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) {
                    // 渲染模板
                    val sw = StringWriter()
                    val tpl = Velocity.getTemplate(template, Constants.UTF8)
                    tpl.merge(context, sw)
                    try {
                        val path = getGenPath(table, template)
                        FileUtils.writeStringToFile(File(path), sw.toString(), CharsetKit.UTF_8)
                    } catch (e: IOException) {
                        throw ServiceException("渲染模板失败，表名：" + table.tableName)
                    }
                }
            }
        }
    }

    /**
     * 同步数据库
     *
     * @param tableName 表名称
     */
    @Transactional
    override fun synchDb(tableName: String?) {
        val table = genTableMapper.selectGenTableByName(tableName)
        val tableColumns: List<GenTableColumn>? = table?.columns
        val tableColumnMap = tableColumns?.associateBy  ({it.columnName},{it})
        val dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName)
        if (MyStringUtils.isEmpty(dbTableColumns)) {
            throw ServiceException("同步数据失败，原表结构不存在")
        }
        val dbTableColumnNames = dbTableColumns.map(GenTableColumn::columnName).toMutableList()
        dbTableColumns.forEach(Consumer { column: GenTableColumn ->
            table?.let { GenUtils.initColumnField(column, it) }
            if (tableColumnMap != null) {
                if (tableColumnMap.containsKey(column.columnName)) {
                    val prevColumn = tableColumnMap[column.columnName]
                    column.columnId = prevColumn!!.columnId
                    if (column.isList()) {
                        // 如果是列表，继续保留查询方式/字典类型选项
                        column.dictType = prevColumn.dictType
                        column.queryType = prevColumn.queryType
                    }
                    if (MyStringUtils.isNotEmpty(prevColumn.isRequired!!) && !column.isPk()
                            && (column.isInsert() || column.isEdit())
                            && (column.isUsableColumn || !column.isSuperColumn)) {
                        // 如果是(新增/修改&非主键/非忽略及父属性)，继续保留必填/显示类型选项
                        column.isRequired = prevColumn.isRequired
                        column.htmlType = prevColumn.htmlType
                    }
                    genTableColumnMapper.updateGenTableColumn(column)
                } else {
                    genTableColumnMapper.insertGenTableColumn(column)
                }
            }
        })
        val delColumns = tableColumns?.filter { column: GenTableColumn? -> !dbTableColumnNames.contains(column!!.columnName) }?.toMutableList()
        if (MyStringUtils.isNotEmpty(delColumns)) {
            genTableColumnMapper.deleteGenTableColumns(delColumns)
        }
    }

    /**
     * 批量生成代码（下载方式）
     *
     * @param tableNames 表数组
     * @return 数据
     */
    override fun downloadCode(tableNames: Array<String>): ByteArray {
        val outputStream = ByteArrayOutputStream()
        val zip = ZipOutputStream(outputStream)
        for (tableName in tableNames) {
            generatorCode(tableName, zip)
        }
        IOUtils.closeQuietly(zip)
        return outputStream.toByteArray()
    }

    /**
     * 查询表信息并生成代码
     */
    private fun generatorCode(tableName: String, zip: ZipOutputStream) {
        // 查询表信息
        val table = genTableMapper.selectGenTableByName(tableName)
        // 设置主子表信息
        setSubTable(table)
        // 设置主键列信息
        setPkColumn(table)
        VelocityInitializer.initVelocity()
        val context = VelocityUtils.prepareContext(table)

        // 获取模板列表
        val templates = VelocityUtils.getTemplateList(table?.tplCategory, table?.tplWebType)
        for (template in templates) {
            // 渲染模板
            val sw = StringWriter()
            val tpl = Velocity.getTemplate(template, Constants.UTF8)
            tpl.merge(context, sw)
            try {
                // 添加到zip
                zip.putNextEntry(ZipEntry(VelocityUtils.getFileName(template, table)))
                IOUtils.write(sw.toString(), zip, Constants.UTF8)
                IOUtils.closeQuietly(sw)
                zip.flush()
                zip.closeEntry()
            } catch (e: IOException) {
                log.error("渲染模板失败，表名：" + table?.tableName, e)
            }
        }
    }

    /**
     * 修改保存参数校验
     *
     * @param genTable 业务信息
     */
    override fun validateEdit(genTable: GenTable) {
        if (GenConstants.TPL_TREE == genTable.tplCategory) {
            val options = JSON.toJSONString(genTable.params)
            val paramsObj = JSON.parseObject(options)
            if (MyStringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) {
                throw ServiceException("树编码字段不能为空")
            } else if (MyStringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) {
                throw ServiceException("树父编码字段不能为空")
            } else if (MyStringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) {
                throw ServiceException("树名称字段不能为空")
            } else if (GenConstants.TPL_SUB == genTable.tplCategory) {
                if (MyStringUtils.isEmpty(genTable.subTableName!!)) {
                    throw ServiceException("关联子表的表名不能为空")
                } else if (MyStringUtils.isEmpty(genTable.subTableFkName!!)) {
                    throw ServiceException("子表关联的外键名不能为空")
                }
            }
        }
    }

    /**
     * 设置主键列信息
     *
     * @param table 业务表信息
     */
    fun setPkColumn(table: GenTable?) {
        for (column in table!!.columns!!) {
            if (column.isPk()) {
                table.pkColumn = column
                break
            }
        }
        if (MyStringUtils.isNull(table.pkColumn)) {
            table.pkColumn = table.columns!![0]
        }
        if (GenConstants.TPL_SUB == table.tplCategory) {
            for (column in table.subTable!!.columns!!) {
                if (column.isPk()) {
                    table.subTable!!.pkColumn = column
                    break
                }
            }
            if (MyStringUtils.isNull(table.subTable!!.pkColumn)) {
                table.subTable!!.pkColumn = table.subTable!!.columns!![0]
            }
        }
    }

    /**
     * 设置主子表信息
     *
     * @param table 业务表信息
     */
    fun setSubTable(table: GenTable?) {
        val subTableName = table!!.subTableName
        if (MyStringUtils.isNotEmpty(subTableName!!)) {
            table.subTable = genTableMapper.selectGenTableByName(subTableName)
        }
    }

    /**
     * 设置代码生成其他选项值
     *
     * @param genTable 设置后的生成对象
     */
    fun setTableFromOptions(genTable: GenTable?) {
        val paramsObj = JSON.parseObject(genTable!!.options)
        if (MyStringUtils.isNotNull(paramsObj)) {
            val treeCode = paramsObj.getString(GenConstants.TREE_CODE)
            val treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE)
            val treeName = paramsObj.getString(GenConstants.TREE_NAME)
            val parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID)
            val parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME)
            genTable.treeCode = treeCode
            genTable.treeParentCode = treeParentCode
            genTable.treeName = treeName
            genTable.parentMenuId = parentMenuId
            genTable.parentMenuName = parentMenuName
        }
    }

    companion object {
        private val log = LoggerFactory.getLogger(GenTableServiceImpl::class.java)

        /**
         * 获取代码生成地址
         *
         * @param table 业务表信息
         * @param template 模板文件路径
         * @return 生成地址
         */
        fun getGenPath(table: GenTable?, template: String?): String {
            val genPath = table!!.genPath
            return if (genPath== "/") {
                System.getProperty("user.dir") + File.separator + "src" + File.separator + template?.let { VelocityUtils.getFileName(it, table) }
            } else genPath + File.separator + template?.let { VelocityUtils.getFileName(it, table) }
        }
    }
}
